package com.jvm123.minio.service;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.Assert;

import com.google.common.collect.Multimap;
import com.jvm123.minio.config.MinioProperties;
import com.jvm123.minio.service.client.ExtendMinioClient;
import com.jvm123.minio.service.client.MinioClientProvider;
import com.jvm123.minio.service.temp.TempCleanService;

import io.minio.AbortMultipartUploadResponse;
import io.minio.BucketExistsArgs;
import io.minio.GetObjectArgs;
import io.minio.ListPartsResponse;
import io.minio.MakeBucketArgs;
import io.minio.ObjectWriteArgs;
import io.minio.ObjectWriteResponse;
import io.minio.PutObjectArgs;
import io.minio.RemoveBucketArgs;
import io.minio.RemoveObjectArgs;
import io.minio.UploadPartResponse;
import io.minio.errors.ErrorResponseException;
import io.minio.errors.InsufficientDataException;
import io.minio.errors.InternalException;
import io.minio.errors.InvalidResponseException;
import io.minio.errors.ServerException;
import io.minio.errors.XmlParserException;
import io.minio.messages.Part;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;

/**
 * 使用minio的api实现文件的存储获取等功能
 *
 * @author yawn http://jvm123.com 2020/1/15 16:04
 */
@Slf4j
public class MinioFileService {

	private MinioProperties properties;
	private TempCleanService tempCleanService;
	private MinioClientProvider clientProvider;

	private String bucket;
	private String tmpDir;

	public MinioFileService(MinioProperties properties, TempCleanService tempCleanService,
			MinioClientProvider clientProvider) {
		super();
		this.properties = properties;
		this.tempCleanService = tempCleanService;
		this.clientProvider = clientProvider;
	}

	@PostConstruct
	private void init() {
		this.bucket = properties.getBucket();
		this.tmpDir = properties.getTmpDir();
		if (properties.getTmpClean()) {
			Date firstTmpCleanTime = properties.getTmpFirstCleanTime();
			if (firstTmpCleanTime == null) {
				firstTmpCleanTime = new Date();
			}
			Long tmpCleanPeriod = properties.getTmpCleanPeriod();
			Long tmpAliveDuration = properties.getTmpAliveDuration();
			initTmpCleaner(firstTmpCleanTime, tmpCleanPeriod, tmpAliveDuration);
		}
	}

	private void initTmpCleaner(Date time, Long period, Long duration) {
		Timer timer = new Timer();
		timer.schedule(new TimerTask() {
			@Override
			public void run() {
				tempCleanService.cleanTemp(tmpDir, duration);
			}
		}, time, period);
	}

	private ExtendMinioClient connect() {
		ExtendMinioClient minioClient = clientProvider.getClient(properties.getEndpoint(), properties.getAccessKey(), properties.getSecretKey());
		log.debug("Got the client to minIO server {}.", properties.getEndpoint());
		return minioClient;
	}

	// service for users below

	public boolean createBucket(String bucketName) {
		assertBucket(bucketName);
		try {
			ExtendMinioClient connect = connect();
			if (connect.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
				return true;
			} else {
				connect.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
				return true;
			}
		} catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException
				| InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException
				| XmlParserException e) {
			log.error("error: {}", e);
			return false;
		}
	}

	public boolean deleteBucket(String bucketName) {
		assertBucket(bucketName);
		try {
			ExtendMinioClient connect = connect();
			if (connect.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
				connect.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
				return true;
			} else {
				return true;
			}
		} catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException
				| InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException
				| XmlParserException e) {
			log.error("error: {}", e);
			return false;
		}
	}

	public String save(File file, String fileName) {
		return save(bucket, file, fileName);
	}

	public boolean delete(String fileName) {
		return delete(bucket, fileName);
	}

	public InputStream getStream(String fileName) {
		return getStream(bucket, fileName);
	}

	public File getFile(String fileName) {
		return getFile(bucket, fileName);
	}

	public void writeTo(String fileName, OutputStream os) throws IOException {
		writeTo(bucket, fileName, os);
	}

	public String save(String bucket, File file) {
		return save(bucket, file, null);
	}

	public String save(String bucket, File file, String destFileName) {
		assertBucket(bucket);
		if (StringUtils.isBlank(destFileName)) {
			destFileName = file.getName();
		}
		try {
			ExtendMinioClient minioClient = connect();
			checkBucket(minioClient, bucket);
			minioClient.putObject(PutObjectArgs.builder().bucket(bucket).object(destFileName)
					.stream(new FileInputStream(file), file.length(), -1).build());
			return destFileName;
		} catch (NoSuchAlgorithmException | InsufficientDataException | IOException | InvalidKeyException
				| ServerException | XmlParserException | ErrorResponseException | InternalException
				| InvalidResponseException e) {
			log.error("error: {}", e);
			return null;
		}
	}

	private void checkBucket(ExtendMinioClient minioClient, String bucket) throws InvalidKeyException,
			ErrorResponseException, InsufficientDataException, InternalException, InvalidResponseException,
			NoSuchAlgorithmException, ServerException, XmlParserException, IllegalArgumentException, IOException {
		assertBucket(bucket);
		boolean isExist = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucket).build());
		if (isExist) {
			log.info("Bucket {} already exists.", bucket);
		} else {
			minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucket).build());
		}
	}

	public String save(String bucket, InputStream is, String objectName) {
		assertBoName(bucket, objectName);
		try {
			ExtendMinioClient minioClient = connect();
			checkBucket(minioClient, bucket);
			minioClient
					.putObject(PutObjectArgs.builder().bucket(bucket).object(objectName).stream(is, -1, ObjectWriteArgs.MIN_MULTIPART_SIZE).build());
			return objectName;
		} catch (NoSuchAlgorithmException | InsufficientDataException | IOException | InvalidKeyException
				| ServerException | XmlParserException | ErrorResponseException | InternalException
				| InvalidResponseException e) {
			log.error("error: {}", e);
			return null;
		}
	}

	public boolean delete(String bucket, String objectName) {
		try {
			assertBoName(bucket, objectName);
			ExtendMinioClient connect = connect();
			connect.removeObject(RemoveObjectArgs.builder().bucket(bucket).object(objectName).build());
			return true;
		} catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException
				| InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException
				| XmlParserException e) {
			log.error("error: {}", e);
			return false;
		}
	}

	public InputStream getStream(String bucket, String objectName) {
		assertBoName(bucket, objectName);
		InputStream is = null;
		try {
			ExtendMinioClient minioClient = connect();
			is = minioClient.getObject(GetObjectArgs.builder().bucket(bucket).object(objectName).build());
		} catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException
				| InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException
				| XmlParserException e) {
			log.error("error: {}", e);
		}
		return is;
	}

	public File getFile(String bucket, String objectName) {
		assertBoName(bucket, objectName);
		InputStream is = getStream(bucket, objectName);
		File dir = new File(tmpDir);
		if (!dir.exists() || dir.isFile()) {
			dir.mkdirs();
		}
		File file = new File(tmpDir + objectName);
		try {
			FileUtils.copyToFile(is, file);
		} catch (IOException e) {
			log.error("error: {}", e);
		}
		return file;
	}

	public void writeTo(String bucket, String objectName, OutputStream os) throws IOException {
		assertBoName(bucket, objectName);
		InputStream is = getStream(bucket, objectName);
		byte[] bytes = new byte[1024];
		int len;
		while ((len = is.read(bytes)) != -1) {
			os.write(bytes, 0, len);
		}
		os.flush();
	}

	/**
	 * 获取uploadId
	 * @param bucketName
	 * @param region
	 * @param objectName
	 * @param headers
	 * @param extraQueryParams
	 * @return
	 * @throws InvalidKeyException
	 * @throws NoSuchAlgorithmException
	 * @throws InsufficientDataException
	 * @throws ServerException
	 * @throws XmlParserException
	 * @throws ErrorResponseException
	 * @throws InternalException
	 * @throws InvalidResponseException
	 * @throws IOException
	 */
	public String getUploadId(String bucketName, String region, String objectName, Multimap<String, String> headers,
			Multimap<String, String> extraQueryParams)
			throws InvalidKeyException, NoSuchAlgorithmException, InsufficientDataException, ServerException,
			XmlParserException, ErrorResponseException, InternalException, InvalidResponseException, IOException {
		assertBoName(bucketName, objectName);
		ExtendMinioClient minioClient = connect();
		checkBucket(minioClient, bucketName);
		return minioClient.getUploadId(bucketName, region, objectName, headers,
				extraQueryParams);
	}

	/**
	 * 上传分片数据
	 */
	public UploadPartResponse uploadPart(String bucketName, String region, String objectName, Object data, int length,
			String uploadId, int partNumber, Multimap<String, String> extraHeaders,
			Multimap<String, String> extraQueryParams)
			throws NoSuchAlgorithmException, InsufficientDataException, IOException, InvalidKeyException,
			ServerException, XmlParserException, ErrorResponseException, InternalException, InvalidResponseException {
		assertBoName(bucketName, objectName);
		ExtendMinioClient minioClient = connect();
		checkBucket(minioClient, bucketName);
		return minioClient.uploadPart(bucketName, region, objectName, data, length, uploadId, partNumber, extraHeaders,
				extraQueryParams);
	}

	/**
	 * 完成分片上传
	 */
	public ObjectWriteResponse completeMultipartUpload(String bucketName, String region, String objectName,
			String uploadId, Part[] parts, Multimap<String, String> extraHeaders,
			Multimap<String, String> extraQueryParams)
			throws NoSuchAlgorithmException, InsufficientDataException, IOException, InvalidKeyException,
			ServerException, XmlParserException, ErrorResponseException, InternalException, InvalidResponseException {
		return connect().completeMultipartUpload(bucketName, region, objectName, uploadId, parts, extraHeaders,
				extraQueryParams);
	}

	/**
	 * 取消分片上传
	 */
	public AbortMultipartUploadResponse abortMultipartUpload(String bucketName, String region, String objectName,
			String uploadId, Multimap<String, String> extraHeaders, Multimap<String, String> extraQueryParams)
			throws NoSuchAlgorithmException, InsufficientDataException, IOException, InvalidKeyException,
			ServerException, XmlParserException, ErrorResponseException, InternalException, InvalidResponseException {
		assertBoName(bucketName, objectName);
		return connect().abortMultipartUpload(bucketName, region, objectName, uploadId, extraHeaders, extraQueryParams);
	}

	/**
	 * 列举分片
	 * @param bucketName
	 * @param region
	 * @param objectName
	 * @param maxParts
	 * @param partNumberMarker
	 * @param uploadId
	 * @param extraHeaders
	 * @param extraQueryParams
	 * @return
	 * @throws NoSuchAlgorithmException
	 * @throws InsufficientDataException
	 * @throws IOException
	 * @throws InvalidKeyException
	 * @throws ServerException
	 * @throws XmlParserException
	 * @throws ErrorResponseException
	 * @throws InternalException
	 * @throws InvalidResponseException
	 */
	public ListPartsResponse listParts(
		      String bucketName,
		      String region,
		      String objectName,
		      Integer maxParts,
		      Integer partNumberMarker,
		      String uploadId,
		      Multimap<String, String> extraHeaders,
		      Multimap<String, String> extraQueryParams)
		      throws NoSuchAlgorithmException, InsufficientDataException, IOException, InvalidKeyException,
		          ServerException, XmlParserException, ErrorResponseException, InternalException,
		          InvalidResponseException {
		assertBoName(bucketName, objectName);
		return connect().listParts(bucketName, region, objectName, maxParts, partNumberMarker, uploadId, extraHeaders,
				extraQueryParams);
	}
	
	/**
	 * 检验bucketName非空
	 * @param bucketName
	 * @param objectName
	 */
	private void assertBucket(String bucketName) {
		Assert.isTrue(StringUtils.isNotBlank(bucketName), "Bucket name cannot be blank.");
	}
	
	/**
	 * 检验bucketName和objectName非空
	 * @param bucketName
	 * @param objectName
	 */
	private void assertBoName(String bucketName, String objectName) {
		assertBucket(bucketName);
		Assert.isTrue(StringUtils.isNotBlank(objectName), "Object name cannot be blank.");
	}
}
